﻿namespace Mercury.ParticleEngine
{
    using System;
    using FluentAssertions;
    using Xunit;
    using Xunit.Extensions;

    // ReSharper disable InconsistentNaming

    public class CalculatorTests
    {
        public class ClampMethod
        {
            [Fact]
            [Trait("Type", "Calculator")]
            public void WhenValueLessThanLowerBound_ReturnsLowerBound()
            {
                const Single value = -10f;

                Calculator.Clamp(value, 0f, 1f).Should<Single>().Be(0f);
            }

            [Fact]
            [Trait("Type", "Calculator")]
            public void WhenValueLessThanLowerBoundOfInterval_ReturnsLowerBound()
            {
                const Single value = -10f;
                
                var interval = new IntervalF(0f, 1f);

                Calculator.Clamp(value, interval).Should<Single>().Be(0f);
            }

            [Fact]
            [Trait("Type", "Calculator")]
            public void WhenValueGreaterThanUpperBound_ReturnsUpperBound()
            {
                const Single value = 10f;

                Calculator.Clamp(value, 0f, 1f).Should<Single>().Be(1f);
            }

            [Fact]
            [Trait("Type", "Calculator")]
            public void WhenValueGreaterThanUpperBoundOfInterval_ReturnsUpperBound()
            {
                const Single value = 10f;

                var interval = new IntervalF(0f, 1f);

                Calculator.Clamp(value, interval).Should<Single>().Be(1f);
            }

            [Fact]
            [Trait("Type", "Calculator")]
            public void WhenValueWithinBounds_ReturnsValue()
            {
                const Single value = 0.5f;

                Calculator.Clamp(value, 0f, 1f).Should<Single>().Be(value);
            }

            [Fact]
            [Trait("Type", "Calculator")]
            public void WhenValueWithinInterval_ReturnsValue()
            {
                const Single value = 0.5f;

                var interval = new IntervalF(0f, 1f);

                Calculator.Clamp(value, interval).Should<Single>().Be(value);
            }
        }

        public class WrapMethod
        {
            [Fact]
            [Trait("Type", "Calculator")]
            public void WhenValueGreaterThanUpperBound_ReturnsWrappedValue()
            {
                const Single value = 10f;

                Calculator.Wrap(value, 0f, 1f).Should().BeInRange(0f, 1f);
            }

            [Fact]
            [Trait("Type", "Calculator")]
            public void WhenValueGreaterThanUpperBoundOfInterval_ReturnsWrappedValue()
            {
                const Single value = 10f;

                var interval = new IntervalF(0f, 1f);

                Calculator.Wrap(value, interval).Should().BeInRange(0f, 1f);
            }

            [Fact]
            [Trait("Type", "Calculator")]
            public void WhenValueWithinBounds_ReturnsValue()
            {
                const Single value = 0.5f;

                Calculator.Wrap(value, 0f, 1f).Should<Single>().Be(value);
            }

            [Fact]
            [Trait("Type", "Calculator")]
            public void WhenValueWithinInterval_ReturnsValue()
            {
                const Single value = 0.5f;

                var interval = new IntervalF(0f, 1f);

                Calculator.Wrap(value, interval).Should<Single>().Be(value);
            }
        }

        public class CalculateEpsilonMethod
        {
            [Fact]
            [Trait("Type", "Calculator")]
            public void WhenGivenVerySmallValue_ReturnsEpsilon()
            {
                const Single value = Single.Epsilon;

                var epsilon = Calculator.CalculateEpsilon(value);

                (value + epsilon).Should().BeGreaterThan(value);
            }

            [Fact]
            [Trait("Type", "Calculator")]
            public void WhenGivenVeryLargeValue_ReturnsEpsilon()
            {
                const Single value = Single.MaxValue * 0.999f;

                var epsilon = Calculator.CalculateEpsilon(value);

                (value + epsilon).Should().BeGreaterThan(value);
            }
        }

        public class LinearInterpolateMethod
        {
            [Theory]
            [Trait("Type", "Calculator")]
            [InlineData(0f, 1f, 0f, 0f)]
            [InlineData(0f, 1f, 1f, 1f)]
            [InlineData(0f, 1f, 0.5f, 0.5f)]
            public void WhenGivenValues_ReturnsInterpolatedValue(Single value1, Single value2, Single amount, Single expected)
            {
                var actual = Calculator.LinearInterpolate(value1, value2, amount);

                actual.Should<Single>().Be(expected);
            }

            [Theory]
            [Trait("Type", "Calculator")]
            [InlineData(0f, 1f, 0f, 0f)]
            [InlineData(0f, 1f, 1f, 1f)]
            [InlineData(0f, 1f, 0.5f, 0.5f)]
            public void WhenGivenInterval_ReturnsInterpolatedValue(Single value1, Single value2, Single amount, Single expected)
            {
                var interval = new IntervalF(value1, value2);

                var actual = Calculator.LinearInterpolate(interval, amount);

                actual.Should<Single>().Be(expected);
            }

            [Theory]
            [Trait("Type", "Calculator")]
            [InlineData(0f, 1f, 0.5f, 0f, 0f, 0f)]
            [InlineData(0f, 1f, 0.5f, 0f, 1f, 0f)]
            [InlineData(0f, 1f, 0.5f, 0f, 0.5f, 1f)]
            [InlineData(0f, 1f, 0.5f, 0f, 0.25f, 0.5f)]
            [InlineData(0f, 1f, 0.5f, 0f, 0.75f, 0.5f)]
            public void WhenGivenValuesWithThirdControlPoint_ReturnsInterpolatedValue(Single value1, Single value2, Single value2Position, Single value3, Single amount, Single expected)
            {
                var actual = Calculator.LinearInterpolate(value1, value2, value2Position, value3, amount);

                actual.Should<Single>().Be(expected);
            }
        }

        public class CubicInterpolateMethod
        {
            [Theory]
            [Trait("Type", "Calculator")]
            [InlineData(0f, 1f, 0f, 0f)]
            [InlineData(0f, 1f, 1f, 1f)]
            [InlineData(0f, 1f, 0.5f, 0.5f)]
            [InlineData(0f, 1f, 0.25f, 0.15625f)]
            [InlineData(0f, 1f, 0.75f, 0.84375f)]
            public void WhenGivenValues_ReturnsInterpolatedValue(Single value1, Single value2, Single amount, Single expected)
            {
                var actual = Calculator.CubicInterpolate(value1, value2, amount);

                actual.Should<Single>().Be(expected);
            }

            [Theory]
            [Trait("Type", "Calculator")]
            [InlineData(0f, 1f, 0f, 0f)]
            [InlineData(0f, 1f, 1f, 1f)]
            [InlineData(0f, 1f, 0.5f, 0.5f)]
            [InlineData(0f, 1f, 0.25f, 0.15625f)]
            [InlineData(0f, 1f, 0.75f, 0.84375f)]
            public void WhenGivenInterval_ReturnsInterpolatedValue(Single value1, Single value2, Single amount, Single expected)
            {
                var interval = new IntervalF(value1, value2);

                var actual = Calculator.CubicInterpolate(interval, amount);

                actual.Should<Single>().Be(expected);
            }
        }

        public class MaxMethod
        {
            [Theory]
            [Trait("Type", "Calculator")]
            [InlineData(0f, 1f, 1f)]
            [InlineData(10f, 0f, 10f)]
            [InlineData(-100f, 100f, 100f)]
            public void WhenGivenDifferentSingleValues_ReturnsLargerValue(Single value1, Single value2, Single expected)
            {
                var actual = Calculator.Max(value1, value2);

                actual.Should<Single>().Be(expected);
            }

            [Theory]
            [Trait("Type", "Calculator")]
            [InlineData(0, 1, 1)]
            [InlineData(10, 0, 10)]
            [InlineData(-100, 100, 100)]
            public void WhenGivenDifferentIntegerValues_ReturnsLargerValue(Int32 value1, Int32 value2, Int32 expected)
            {
                var actual = Calculator.Max(value1, value2);

                actual.Should().Be(expected);
            }
        }

        public class MinMethod
        {
            [Theory]
            [Trait("Type", "Calculator")]
            [InlineData(0f, 1f, 0f)]
            [InlineData(10f, 0f, 0f)]
            [InlineData(-100f, 100f, -100f)]
            public void WhenGivenDifferentSingleValues_ReturnsSmallerValue(Single value1, Single value2, Single expected)
            {
                var actual = Calculator.Min(value1, value2);

                actual.Should<Single>().Be(expected);
            }

            [Theory]
            [Trait("Type", "Calculator")]
            [InlineData(0, 1, 0)]
            [InlineData(10, 0, 0)]
            [InlineData(-100, 100, -100)]
            public void WhenGivenDifferentIntegerValues_ReturnsSmallerValue(Int32 value1, Int32 value2, Int32 expected)
            {
                var actual = Calculator.Min(value1, value2);

                actual.Should().Be(expected);
            }
        }

        public class AbsoluteMethod
        {
            [Theory]
            [Trait("Type", "Calculator")]
            [InlineData(1f, 1f)]
            [InlineData(-1f, 1f)]
            public void WhenGivenSingleValue_ReturnsAbsoluteValue(Single value, Single expected)
            {
                var actual = Calculator.Absolute(value);

                actual.Should<Single>().Be(expected);
            }

            [Theory]
            [Trait("Type", "Calculator")]
            [InlineData(1, 1)]
            [InlineData(-1, 1)]
            public void WhenGIvenIntegerValue_ReturnsAbsoluteValue(Int32 value, Int32 expected)
            {
                var actual = Calculator.Absolute(value);

                actual.Should().Be(expected);
            }
        }
    }

    // ReSharper restore InconsistentNaming
}